﻿#region Copyright (c) 2011 R. Uittenbosch
/*
* Copyright (C) 2011 R. Uittenbosch
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
* associated documentation files (the "Software"), to deal in the Software without restriction, including 
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
* following conditions:
* 
* The above copyright notice and this permission notice shall be included in all copies or substantial 
* portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 
* EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Windows.Forms;
using System.Data.Linq;
using DatabaseViewer.Interfaces;
using DatabaseViewer.Domain;

namespace DatabaseViewer
{

    /// <summary>
    /// Visualize a database structure on the hostcontrol
    /// </summary>
    public class DatabaseVisualizer 
    {
        /// <summary>
        /// The tabcontrol containing different tabs for each tabel
        /// </summary>
        private TabControl tabcontrol;

        /// <summary>
        /// The main control on which the database should be visualized
        /// </summary>
        private Control Host;

        /// <summary>
        /// GridView bindings between GridView and database
        /// </summary>
        private Dictionary<string, BindingSource> bindings = new Dictionary<string, BindingSource>();

        /// <summary>
        /// A list of all ControlTypes mapped to the actual controls
        /// </summary>
        private Dictionary<ControlType, Control> controllist;

        /// <summary>
        /// The tree to be visualized
        /// </summary>
        public VisualizerNode Tree {get; set;}

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="hostControl">The hostcontrol on which the tree should be visualized</param>
        /// <param name="tree">The tree to visualize</param>
        /// <param name="controlList">A list of all ControlTypes mapped to the actual controls</param>
        public DatabaseVisualizer(Control hostControl, IVisualizerNode tree, Dictionary<ControlType,Control> controlList )
        {
            Tree        = tree as VisualizerNode;
            Host        = hostControl;
            controllist = controlList;
        }

        /// <summary>
        /// Create a node on the browser and its children
        /// </summary>
        /// <param name="node"></param>
        private void CreateNode(VisualizerNode node)
        {
            // Get the control based on the type we need
            Control nodeControl = controllist[node.Control];

            // If a tab, then create the datagrid on a tab, otherwise fill the different containers.
            if (node.Control == ControlType.Tab)
                CreateDataGridOnTab(nodeControl as TabControl, node.DisplayName, node.ObjectType);                
            else
                CreateDataGridOnControl(nodeControl as Control, node.ObjectType);
            // If it's not the last in the tree, create its children
            if (!node.IsLeaf)
                foreach (VisualizerNode n in node.Children)
                    CreateNode(n);
        }

        /// <summary>
        /// Clear the data in a specific node and its children
        /// </summary>
        /// <param name="node"></param>
        private void ClearNode(VisualizerNode node)
        {
            // First itself
            ClearDataOnPage(node.ObjectType);
            // And the children
            if (!node.IsLeaf)
                foreach (VisualizerNode n in node.Children)
                    ClearNode(n);

        }

        /// <summary>
        /// Concat 2 lists
        /// </summary>
        /// <param name="lista"></param>
        /// <param name="listb"></param>
        /// <returns></returns>
        public IList AddToList(IList lista, IList listb)
        {
            foreach (object o in listb)
                lista.Add(o);
            return lista;
        }

        /// <summary>
        /// Fill a node based on the given object
        /// </summary>
        /// <param name="item"></param>
        /// <param name="node"></param>
        private void FillNode(object item, VisualizerNode node)
        {            
            if (node.Multiple)
            {
                
                if (node.DataSource == null)
                {
                    // Collect all items
                    var list = AddToList(CreateGenericList(node.ObjectType), GetPropertyOfType(item, node.ObjectType));
                    AddDataToPage(list, node.ObjectType);
                    if (!node.IsLeaf)
                        foreach (var i in list)
                            foreach (VisualizerNode n in node.Children)
                                FillNode(i, n); // Fill Child
                }
                else
                {
                    LoadDataOnPage(node.DataSource, node.ObjectType);
                    if (!node.IsLeaf)
                        foreach (var i in node.DataSource)
                            foreach (VisualizerNode n in node.Children)
                                FillNode(i, n); // Fill Child
                }

            }
            else
            {
                // If it's a leaf just load it.
                if (node.IsLeaf)
                {
                    // If no custom datasource, just load a property, otherwise use the custom source
                    if (node.DataSource == null)
                        LoadPropertyOfType(item, node.ObjectType);
                    else
                        LoadDataOnPage(node.DataSource, node.ObjectType);
                        
                }
                else
                {
                    
                    // En nu de kinderen
                    if (node.DataSource != null)
                    {
                        // Het enkelvoudige item laden op de pagina
                        LoadDataOnPage(node.DataSource, node.ObjectType);
                        foreach (var i in node.DataSource)
                            foreach (VisualizerNode n in node.Children)
                                FillNode(i, n);
                    }
                    else
                    {
                        var list = AddToList(CreateGenericList(node.ObjectType), GetPropertyOfType(item, node.ObjectType));
                        // Het enkelvoudige item laden op de pagina
                        list.Remove(null);
                        AddDataToPage(list, node.ObjectType);
                        foreach (var i in list)
                            foreach (VisualizerNode n in node.Children)                                                
                                FillNode(i, n);
                    }
                }                
            }          
        }

        
        /// <summary>
        /// Fill the tree with the given object
        /// </summary>
        /// <param name="item"></param>
        public void VisualizeTree()
        {
            Host.SuspendLayout();
            // First clear everything
            ClearNode(Tree);
            // If no data, show a message
            if (Tree.DataSource == null || Tree.DataSource.Count < 1 || Tree.DataSource[0] == null)
                MessageBox.Show("No data");
            else                                            
                FillNode(Tree.DataSource[0], Tree);

            Host.Invalidate(true);
            Host.ResumeLayout();
        }

        /// <summary>
        /// Create the complete tree on de hostform
        /// </summary>
        public void CreateTreeOnForm()
        {         
            CreateNode(Tree);
            ReOrderTabsByName();
        }

        private void ReOrderTabsByName()
        {
            
            TabControl control = (controllist[ControlType.Tab] as TabControl);            
            List<string> names = new List<string>();
            foreach (TabPage n in control.TabPages)
                names.Add(n.Name);

            names.Sort();

            foreach (string n in names)
            {
                TabPage p = control.TabPages[n];
                control.TabPages.Remove(p);
                control.TabPages.Add(p);
            }           
        }
               
        /// <summary>
        /// Load the specified property of the given source
        /// </summary>
        /// <param name="source"></param>
        /// <param name="propertytype"></param>
        private void LoadPropertyOfType(object source, string propertytype)
        {
            Type reqType = Type.GetType(propertytype);
            if (reqType == null)
                throw new NullReferenceException(string.Format("Type {0} kon niet gevonden worden.", propertytype));
            foreach (PropertyInfo i in source.GetType().GetProperties())
            {
                // If it's a generic type, check the contained type.
                if (i.PropertyType.IsGenericType)
                {
                    Type[] typeArgs = i.PropertyType.GetGenericArguments();
                    foreach (Type t in typeArgs)
                    {
                        // Do the match? Then create the page
                        if (t == reqType)
                        {
                            AddDataToPage(i.GetValue(source, null) as IList, propertytype);
                        }
                    }
                }

                // Do the match? Then create the page
                if (i.PropertyType == reqType)
                {
                    IList list = CreateGenericList(reqType) as IList;
                    list.Add(i.GetValue(source, null));
                    AddDataToPage(list, propertytype);
                }
            }
        }

        /// <summary>
        ///  a datagrid with the specified name
        /// </summary>
        /// <param name="control"></param>
        /// <param name="tekst"></param>
        /// <param name="name"></param>
        private void CreateDataGridOnTab(TabControl control, string tekst, string name)
        {
            if (!bindings.Keys.Contains(name))
            {
                tabcontrol = control;
                TabPage page = new TabPage(tekst) { Name = name };
                bindings.Add(name, new BindingSource());
                DataGridView v = new DataGridView()
                {
                    Name = name,
                    Dock = DockStyle.Fill,
                    AllowUserToAddRows = false,
                    AllowUserToDeleteRows = false,
                    AutoGenerateColumns = true,
                    ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize,
                    ReadOnly = true,
                    DataSource = bindings[name]
                };
                v.VisibleChanged += new EventHandler(v_VisibleChanged);
                v.DataError += new DataGridViewDataErrorEventHandler(v_DataError);
                page.Controls.Add(v);
                control.TabPages.Add(page);
            }
        }


        /// <summary>
        /// Create a datagrid with the specified name
        /// </summary>
        /// <param name="panel"></param>
        /// <param name="name"></param>
        private void CreateDataGridOnControl(Control panel, string name)
        {
            if (!bindings.Keys.Contains(name))
            {
                panel.Controls.Clear();
                bindings.Add(name, new BindingSource());
                DataGridView v = new DataGridView()
                {
                    Name = name,
                    Dock = DockStyle.Fill,
                    AllowUserToAddRows = false,
                    AllowUserToDeleteRows = false,
                    AutoGenerateColumns = true,
                    ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize,
                    ReadOnly = true,
                    DataSource = bindings[name]
                };

                v.DataError += new DataGridViewDataErrorEventHandler(v_DataError);
                panel.Controls.Add(v);
            }
        }

        private void HideComplexColumns(DataGridView v)
        {
            foreach (DataGridViewColumn c in v.Columns)
            {
                if (!c.ValueType.ToString().Contains("System"))
                    c.Visible = false;
                c.AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
            }
        }


        /// <summary>
        /// VisibleChanged event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void v_VisibleChanged(object sender, EventArgs e)
        {
            DataGridView v = sender as DataGridView;
            if (v != null)
            {
                v.AutoGenerateColumns = false;
                v.Invalidate();
                v.Update();
                v.AutoGenerateColumns = true;

                HideComplexColumns(v);
              
            }
        }

        /// <summary>
        /// Do nothing when a load error occures
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void v_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            //
        }

        /// <summary>
        /// Find the grid with the specified name.
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private DataGridView GetGridOnPanel(Control parent, string name)
        {

            DataGridView view = null;
            // Loop through all controls
            foreach (Control c in parent.Controls)
            {
                // Is it a datagrid?
                if (c as DataGridView != null && c.Name == name)
                    return c as DataGridView;
                else
                {
                    // Find grid on this control
                    view = GetGridOnPanel(c, name);
                    // Did we find it? The exit
                    if (view != null)
                        return view;
                }
            }
            return view;
        }

        /// <summary>
        /// Get a datagrid by it's name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private DataGridView GetGridViewByName(string name)
        {
            // First try on a tab,
            DataGridView view = GetGridOnTab(name);
            // Otherwise, try one of the higher panels
            if (view == null)
                view = GetGridOnPanel(Host, name);
              return view;
        }

        /// <summary>
        /// The the grids bindingsource by name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private BindingSource GetGridByName(string name)
        {
            return bindings[name];          
        }

        /// <summary>
        /// Get the datagrid on a tab by name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private DataGridView GetGridOnTab(string name)
        {
            DataGridView view = null;
            if (tabcontrol != null)
            {
                foreach (TabPage p in tabcontrol.TabPages)
                {
                    if (p.Name == name)
                    {
                        foreach (Control c in p.Controls)
                        {
                            if (c.Name == name && c as DataGridView != null)
                            {
                                view = c as DataGridView;
                            }
                        }
                    }
                }
            }
            return view;
        }

        /// <summary>
        /// Load the specified data on the page
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        private void LoadDataOnPage<T>(EntitySet<T> data) where T : class
        {
            List<T> list = new List<T>();
            if (data != null)
            {                
                list.AddRange(data);               
            }
            LoadDataOnPage<T>(list);
        }

        /// <summary>
        /// Load the specified properties of a source object on the page
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="propertyTypes"></param>
        private void LoadProperties<T>(List<T> source, List<string> propertyTypes)
        {
            // Clear All
            foreach (string type in propertyTypes)
                ClearDataOnPage(type);

            foreach (object o in source)
            {
                LoadProperties(o, propertyTypes, true);
            }

        }

        /// <summary>
        /// Get the list of properties of a certain type on the given source object
        /// </summary>
        /// <param name="source"></param>
        /// <param name="propertytype"></param>
        /// <returns></returns>
        private IList GetPropertyOfType(object source, string propertytype)
        {
            Type reqType = Type.GetType(propertytype);
            if (reqType == null)
                throw new NullReferenceException(string.Format("Type {0} kon niet gevonden worden.", propertytype));

            foreach (PropertyInfo i in source.GetType().GetProperties())
            {
                if (i.PropertyType.IsGenericType)
                {
                    Type[] typeArgs = i.PropertyType.GetGenericArguments();
                    foreach (Type t in typeArgs)
                    {
                        if (t == reqType)
                        {
                            return i.GetValue(source, null) as IList;
                        }
                    }
                }
                if (i.PropertyType == reqType)
                {
                    IList list = CreateGenericList(reqType) as IList;
                    list.Add(i.GetValue(source, null));
                    return list;
                }
            }
            throw new NullReferenceException(string.Format("Type {0} kon niet gevonden worden op object {1}.", propertytype, source.GetType()));
            return null;
        }

        /// <summary>
        /// Create a generic list based on a stringbases type
        /// </summary>
        /// <param name="innerType"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private IList CreateGenericList(string innerType, params object[] args)
        {
            Type t = Type.GetType(innerType);
            if (t == null)
                throw new NullReferenceException(string.Format("Type {0} kon niet gevonden worden.", innerType));
            return CreateGenericList(t, args);
        }

        /// <summary>
        /// Create a generic list based on the given type
        /// </summary>
        /// <param name="innerType"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private IList CreateGenericList(Type innerType, params object[] args)
        {
            
            Type generic = typeof(List<>);
            System.Type specificType = generic.MakeGenericType(new System.Type[] { innerType });
            return Activator.CreateInstance(specificType, args) as IList;
        }

        /// <summary>
        /// Load the properties of the given source object
        /// </summary>
        /// <param name="source"></param>
        /// <param name="propertyTypes"></param>
        private void LoadProperties(object source, List<string> propertyTypes)
        {
            LoadProperties(source, propertyTypes, false);
        }

        /// <summary>
        /// Load the properties of the given source object
        /// </summary>
        /// <param name="source"></param>
        /// <param name="propertyTypes"></param>
        /// <param name="add"></param>
        private void LoadProperties(object source, List<string> propertyTypes, bool add)
        {
            foreach (string type in propertyTypes)
            {
                if (!add)
                    ClearDataOnPage(type);

                LoadPropertyOfType(source, type);
            }
        }

        /// <summary>
        /// Load the given type on a gridview
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <param name="type"></param>
        private void LoadDataOnPage<T>(T data, string type) where T : class 
        {
            IList l = CreateGenericList(type);
            l.Add(data);
            GetGridViewByName(type).AutoGenerateColumns = true;
            GetGridByName(type).DataSource = l;

            RedrawGrid(type, data);
        }

        /// <summary>
        /// Load the given type on a gridview
        /// </summary>
        /// <param name="data"></param>
        /// <param name="type"></param>
        private void LoadDataOnPage(IList data, string type)
        {
            if (data == null)
                GetGridByName(type).DataSource = CreateGenericList(type);
            else
                GetGridByName(type).DataSource = data;

            RedrawGrid(type, data);   
        }

        /// <summary>
        /// Load the given type on a gridview
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        private void LoadDataOnPage<T>(List<T> data) where T : class
        {
            GetGridByName(typeof(T).ToString()).DataSource = data;
            RedrawGrid(typeof(T).ToString(), data);        
        }

        void RedrawGrid(string type, object data)
        {
            DataGridView v = GetGridViewByName(type);
            if (v.Visible) v.AutoGenerateColumns = false;
            GetGridByName(type).DataSource = data;
            int nr = 3;
            if ((data as IList) != null)
                nr = (data as IList).Count;
            
            MakeGridViewVisible(v, nr);            
            if (v.Visible) v.AutoGenerateColumns = true;
            HideComplexColumns(v);
          
        }

        /// <summary>
        /// Load the given type on a gridview
        /// </summary>
        /// <param name="data"></param>
        /// <param name="type"></param>
        private void AddDataToPage(IList data, string type)
        {
            if (data != null)
            {
                IList existing = GetGridByName(type).DataSource as IList;
                if (existing == null)
                {
                    Type t = Type.GetType(type);
                    if (t == null)
                        throw new NullReferenceException(string.Format("Type {0} kon niet gevonden worden.", type));

                    existing = CreateGenericList(t) as IList;

                }
                foreach (object o in data)
                    existing.Add(o);

                RedrawGrid(type, existing);                
            }

        }


        /// <summary>
        /// Load the given type on a gridview
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <param name="type"></param>
        private void AddDataToPage<T>(T data, string type) where T : class
        {
            List<T> list = new List<T>();
            list.Add(data);
            AddDataToPage<T>(list);

        }

        /// <summary>
        /// Load the given type on a gridview
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        private void AddDataToPage<T>(EntitySet<T> data) where T : class
        {
            if (data != null)
            {
                List<T> list = new List<T>();
                list.AddRange(data);
                AddDataToPage<T>(list);
            }
        }

        /// <summary>
        /// Load the given type on a gridview
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        private void AddDataToPage<T>(T data) where T : class
        {
            AddDataToPage(new List<T>() { data });
        }


        /// <summary>
        /// Load the given type on a gridview
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        private void AddDataToPage<T>(List<T> data) where T : class
        {
            if (data != null)
            {
                string type = typeof(T).ToString();
                List<T> existing = GetGridByName(type).DataSource as List<T>;
                if (existing == null)
                {
                    existing = new List<T>();
                    GetGridByName(type).DataSource = existing;
                }
                existing.AddRange(data);
                RedrawGrid(type, existing);
            }
        }


        /// <summary>
        /// Autoformat the given datagridview so it's completely visible. (to a max offcourse)
        /// </summary>
        /// <param name="v"></param>
        /// <param name="nrOfRows"></param>
        private void MakeGridViewVisible(DataGridView v, int nrOfRows)
        {
            SplitterPanel p = (v.Parent as SplitterPanel);
            // Alleen splitterpanels zijn supported
            if (p != null)
            {
                // + 2 voor het geval we een scrollbar hebben.
                int newheight = (nrOfRows + 2) * v.RowTemplate.Height;


                try
                {
                    // Als de oude hoogte te klein is, verplaats dan de splitter
                    (p.Parent as SplitContainer).SplitterDistance += newheight - v.Height;
                    if ((p.Parent as SplitContainer).SplitterDistance > 150)
                        (p.Parent as SplitContainer).SplitterDistance = 150;
                }
                catch (Exception e)
                { }
            }
            
 
        }

       /// <summary>
       /// Clear the datagridview
       /// </summary>
       /// <param name="type"></param>
        private void ClearDataOnPage(string type)
        {
            if (bindings[type].DataSource != null)
            {
                (bindings[type].DataSource as IList).Clear();
                
            }
            
            DataGridView v = GetGridViewByName(type);
            v.AutoGenerateColumns = true;
            v.Columns.Clear();
            //if (v.Parent as TabPage != null)
            //{
            //    bindings[type].DataSource = null;

            //    v.Parent.SuspendLayout();
            //    v.SuspendLayout();
            //    if (v.DataSource as IList != null)
            //        (v.DataSource as IList).Clear();
            //    else
            //        v.DataSource = CreateGenericList(Type.GetType(type));
            //    v.Invalidate();
            //    v.Update();
            //    v.ResumeLayout();
            //    v.Parent.ResumeLayout();
            //}
        }

        /// <summary>
        /// Clear the datagridview
        /// </summary>
        /// <typeparam name="T"></typeparam>
        private void ClearDataOnPage<T>() where T : class
        {
            GetGridByName(typeof(T).ToString()).DataSource = new List<T>();
        }

      

    }
}
